// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Internal;

namespace System.ComponentModel.Composition
{
    partial class AdaptingExportProvider
    {
        // Describes an adapter type that adapts from one contract to another
        private class AdapterDefinition
        {
            public AdapterDefinition(Export export)
            {
                Assumes.NotNull(export);

                this.Export = export;
                this.FromContractName = this.GetContractName(CompositionServices.AdapterFromContractMetadataName);
                this.ToContractName = this.GetContractName(CompositionServices.AdapterToContractMetadataName);
            }

            public Export Export
            {
                get;
                private set;
            }

            public string FromContractName
            {
                get;
                private set;
            }

            public string ToContractName
            {
                get;
                private set;
            }

            public bool HasValidMetadata()
            {
                if (string.IsNullOrEmpty(FromContractName))
                {
                    return false;
                }

                return !string.IsNullOrEmpty(ToContractName);
            }

            public CompositionResult<Export> Adapt(Export exportToAdapt)
            {
                Assumes.NotNull(exportToAdapt);

                // We validate the metadata every attempted usage of the adapter,
                // that way if there is an error, then it is raised everytime 
                // someone attempts to request the contract name it adapts to
                var result = this.ValidateMetadata();
                if (!result.Succeeded)
                {
                    return result.ToResult<Export>(null);
                }

                CompositionResult<Func<Export, Export>> valueResult = this.GetAdapterMethod();
                if (!valueResult.Succeeded)
                {   // Failed to get adapter method

                    return result.MergeResult(valueResult.ToResultWithNoValue()).ToResult<Export>(null);
                }

                return this.AdaptCore(valueResult.Value, exportToAdapt);
            }

            private CompositionResult<Export> AdaptCore(Func<Export, Export> adapt, Export exportToAdapt)
            {
                Assumes.NotNull(adapt, exportToAdapt);

                var result = CompositionResult.SucceededResult;

                Exception exception;
                Export adaptedExport = AdaptCoreWrappingInCatch(adapt, exportToAdapt, out exception);

                if (exception != null)
                {   // Adapter threw unhandled exception

                    result = result.MergeIssue(CompositionIssue.CreateIssueAsError(
                        CompositionIssueId.ExceptionDuringAdapt,
                        exception,
                        Strings.ExceptionDuringAdapt,
                        this.ToContractName,
                        this.FromContractName));
                    return result.ToResult<Export>(null);
                }

                if (adaptedExport != null)
                {
                    result = CheckAdaptation(adaptedExport);
                    if (!result.Succeeded)
                    {
                        return result.ToResult<Export>(null);
                    }
                }

                return result.ToResult<Export>(adaptedExport);
            }

            private CompositionResult CheckAdaptation(Export adaptedExport)
            {
                CompositionResult result = CompositionResult.SucceededResult;

                if (!StringComparers.ContractName.Equals(adaptedExport.Definition.ContractName, ToContractName))
                {   // The returned export must match the adapter's 'To' contract name

                    result = result.MergeIssue(CompositionIssue.CreateIssueAsError(
                        CompositionIssueId.AdapterContractMismatch,
                        Strings.AdapterContractMismatch,
                        this.ToContractName,
                        adaptedExport.Definition.ContractName
                        ));
                }

                return result;
            }

            [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
            private static Export AdaptCoreWrappingInCatch(Func<Export, Export> adapt, Export exportToAdapt, out Exception exception)
            {
                exception = null;

                try
                {
                    return adapt(exportToAdapt);
                }
                catch (Exception ex)
                {
                    exception = ex;
                    return null;
                }
            }

            private string GetContractName(string name)
            {
                // Similar to the ExportAttribute, we allow the contract name to be 
                // adapted to/from to be either specified as a type or as a string
                Type type = this.Export.Metadata.GetValue<Type>(name);
                if (type != null)
                {
                    return CompositionServices.GetContractName(type);
                }

                return this.Export.Metadata.GetValue<string>(name);
            }

            private CompositionResult ValidateMetadata()
            {
                CompositionResult result = CompositionResult.SucceededResult;

                if (StringComparers.ContractName.Equals(this.ToContractName, this.FromContractName))
                {
                    return result.MergeIssue(CompositionIssue.CreateIssueAsError(
                        CompositionIssueId.AdapterCannotAdaptFromAndToSameContract,
                        Strings.AdapterCannotAdaptFromAndToSameContract,
                        this.ToContractName));
                }

                return result;
            }

            private CompositionResult<Func<Export, Export>> GetAdapterMethod()
            {
                var result = CompositionResult.SucceededResult;

                var valueResult = this.Export.TryGetExportedObject();
                if (!valueResult.Succeeded)
                {
                    return result.MergeResult(valueResult.ToResultWithNoValue()).ToResult<Func<Export, Export>>(null);
                }

                Func<Export, Export> export = valueResult.Value as Func<Export, Export>;
                if (export == null)
                {
                    return result.MergeIssue(CompositionIssue.CreateIssueAsError(
                        CompositionIssueId.AdapterTypeMismatch,
                        valueResult.Value == null ? Strings.AdapterTypeNull : Strings.AdapterTypeMismatch,
                        valueResult.Value == null ? null : valueResult.Value.GetType().ToString(),
                        typeof(Func<Export, Export>))).ToResult<Func<Export, Export>>(null);

                }

                return result.ToResult(export);
            }
        }
    }
}
